package com.hzlj.position.config.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fz.common.base.converter.QueryWrapperConverter;
import com.fz.common.base.core.db.function.GroupConcatUtil;
import com.fz.common.base.dto.DeleteCommonDTO;
import com.fz.common.base.dto.PageParamDTO;
import com.fz.common.base.dto.QueryPageResultDTO;
import com.fz.common.base.exception.PubError;
import com.fz.common.base.exception.PubException;
import com.fz.common.dict.annotation.DictConvert;
import com.hzlj.position.config.cache.JzJbxxDeviceBindCache;
import com.hzlj.position.config.common.dto.common.LocateTimesDTO;
import com.hzlj.position.config.common.dto.jzJbxxDeviceBind.*;
import com.hzlj.position.config.common.dto.jzJbxxDeviceBindHistory.JzJbxxDeviceBindHistoryAddDTO;
import com.hzlj.position.config.common.enums.CRUDEnum;
import com.hzlj.position.config.common.enums.ChannelType;
import com.hzlj.position.config.common.model.JzJbxxDeviceBind;
import com.hzlj.position.config.config.PositionConfig;
import com.hzlj.position.config.converter.JzJbxxDeviceBindConverter;
import com.hzlj.position.config.dao.JzJbxxDeviceBindDao;
import com.hzlj.position.config.mapper.JzJbxxDeviceBindMapper;
import com.hzlj.position.config.service.JzJbxxDeviceBindHistoryService;
import com.hzlj.position.config.service.JzJbxxDeviceBindService;
import com.hzlj.sqjz.jz.common.dto.jzJbxx.JzJbxxQueryResultDTO;
import com.hzlj.sqjz.jz.service.JzJbxxService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;

import static com.hzlj.position.config.common.constant.Topic.DEVICE_BIND;
import static com.hzlj.position.config.config.IdConfig.POSITION_ID_GEN;
import static java.util.stream.Collectors.*;

/**
 * 设备绑定表(JzJbxxDeviceBind)表服务接口
 *
 * @author lifh
 * @date 2023-03-13 16:57:41
 */
@Slf4j
@Service
public class JzJbxxDeviceBindServiceImpl implements JzJbxxDeviceBindService {
    @Resource
    private JzJbxxService jzJbxxService;
    @Resource
    private JzJbxxDeviceBindDao jzJbxxDeviceBindDao;
    @Resource
    private JzJbxxDeviceBindMapper jzJbxxDeviceBindMapper;
    @Resource
    private JzJbxxDeviceBindCache jzJbxxDeviceBindCache;
    @Resource
    private JzJbxxDeviceBindHistoryService jzJbxxDeviceBindHistoryService;
    @Resource
    private KafkaTemplate<String, JzJbxxDeviceBindMsgDTO> kafkaTemplate;
    @Resource
    private PositionConfig positionConfig;

    /**
     * 通过id查询单条数据
     *
     * @param id id
     * @return 实例对象
     */
    @Override
    public JzJbxxDeviceBindQueryResultDTO getJzJbxxDeviceBindById(String id) {
        JzJbxxDeviceBind jzJbxxDeviceBind = this.jzJbxxDeviceBindDao.getById(id);
        if (Objects.isNull(jzJbxxDeviceBind)) {
            throw new PubException(PubError.NO_OBJECT_FOUND);
        }
        return JzJbxxDeviceBindConverter.from(jzJbxxDeviceBind);
    }

    @Override
    public JzJbxxDeviceBindQueryResultDTO getJzJbxxDeviceBindByJzId(String jzId, String channel) {
        JzJbxxDeviceBind jzJbxxDeviceBind = this.jzJbxxDeviceBindDao.getJzJbxxDeviceBindByJzId(jzId, channel);
        if (Objects.isNull(jzJbxxDeviceBind)) {
            throw new PubException("没有设备绑定信息");
        }
        return JzJbxxDeviceBindConverter.from(jzJbxxDeviceBind);
    }

    /**
     * 新增数据
     *
     * @param dto 实例对象
     * @return 实例对象
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public JzJbxxDeviceBindQueryResultDTO saveJzJbxxDeviceBind(JzJbxxDeviceBindAddDTO dto) {
        //校验重复
        checkRepeat(CRUDEnum.ADD, dto.getJzId(), dto.getDeviceCode(), dto.getChannel());
        JzJbxxDeviceBind jzJbxxDeviceBind = JzJbxxDeviceBindConverter.from(dto);
        jzJbxxDeviceBind.setId(POSITION_ID_GEN.nextIdStr());
        //新增数据
        this.jzJbxxDeviceBindDao.save(jzJbxxDeviceBind);
        log.info("【新增数据成功】:jzJbxxDeviceBind={}", jzJbxxDeviceBind);
        JzJbxxDeviceBindQueryResultDTO result = JzJbxxDeviceBindConverter.from(jzJbxxDeviceBind);
        //发送消息
        syncDeviceBind(CRUDEnum.ADD, result, dto.getCreatorUid(), dto.getCreatorName(), false);

        return result;
    }

    /**
     * 删除数据
     *
     * @param dto 实例对象
     * @return 是否成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteJzJbxxDeviceBind(DeleteCommonDTO<String> dto) {
        List<JzJbxxDeviceBind> jzJbxxDeviceBinds = jzJbxxDeviceBindDao.listByIds(dto.toIds());
        boolean result = this.jzJbxxDeviceBindDao.deleteJzJbxxDeviceBind(dto);
        if (result) {
            jzJbxxDeviceBinds.forEach(deviceBind -> {
                //发送消息
                syncDeviceBind(CRUDEnum.DEL, JzJbxxDeviceBindConverter.from(deviceBind), dto.getUpdaterUid(), deviceBind.getUpdaterName(), true);
                //去掉缓存
                jzJbxxDeviceBindCache.removeJzJbxxDeviceBindCache(deviceBind.getDeviceCode(), deviceBind.getChannel());
            });
            log.info("【删除数据成功】:JzJbxxDeviceBind={}", dto);
        }
        return result;
    }


    /**
     * 修改数据
     *
     * @param dto 实例对象
     * @return 实例对象
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateJzJbxxDeviceBind(JzJbxxDeviceBindUpdateDTO dto) {
        JzJbxxDeviceBind deviceBind = this.jzJbxxDeviceBindDao.getById(dto.getId());
        //更新类型
        CRUDEnum crud = ObjectUtil.equal(deviceBind.getDeviceCode() + ":" + deviceBind.getChannel(), dto.getDeviceCode() + ":" + dto.getChannel())
                ? CRUDEnum.UPDATE : CRUDEnum.UPDATE_DEVICE_CODE;
        //校验重复
        checkRepeat(crud, dto.getJzId(), dto.getDeviceCode(), dto.getChannel());
        JzJbxxDeviceBind jzJbxxDeviceBind = JzJbxxDeviceBindConverter.from(dto);
        boolean update = this.jzJbxxDeviceBindDao.updateJzJbxxDeviceBind(jzJbxxDeviceBind);
        if (update) {
            if (crud.equals(CRUDEnum.UPDATE_DEVICE_CODE)) {
                syncDeviceBind(CRUDEnum.DEL, JzJbxxDeviceBindConverter.from(deviceBind), dto.getUpdaterUid(), dto.getUpdaterName(), false);
            }
            syncDeviceBind(crud, getJzJbxxDeviceBindById(dto.getId()), dto.getUpdaterUid(), dto.getUpdaterName(), false);
            //去掉缓存
            jzJbxxDeviceBindCache.removeJzJbxxDeviceBindCache(deviceBind.getDeviceCode(), deviceBind.getChannel());
            log.info("【更新数据成功】:jzJbxxDeviceBind={}", jzJbxxDeviceBind);
        }
        return update;
    }

    @Override
    public int deleteJzJbxxDeviceBind() {
        List<String> ids = this.jzJbxxDeviceBindDao.ids4NeedDel();
        if (ObjectUtil.isEmpty(ids)) {
            return 0;
        }
        for (String id : ids) {
            log.info("定期清理需要删除的设备绑定数据:id={}", id);
            this.deleteJzJbxxDeviceBind(new DeleteCommonDTO<String>().setId(id));
        }
        return ids.size();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<JzJbxxDeviceBindQueryResultDTO> updateLocateTimes(LocateTimesUpdateDTO dto) {
        //按照jzId分组
        Map<String, List<LocateTimesDTO.Item>> locateTimesByJzId = dto.getItems().stream()
                .flatMap(e -> e.getJzIds().stream().map(jzId -> new Pair<>(jzId, e.getLocateTime())))
                .collect(groupingBy(Pair::getKey, mapping(Pair::getValue, toList())));

        //校验重复
        Set<String> jzIds = locateTimesByJzId.keySet();

        //查询用户
        JzJbxxDeviceBindSimpleQueryParamDTO params = this.getQueryParam4App();
        params.setJzIds(new ArrayList<>(jzIds));
        List<JzJbxxDeviceBindQueryResultDTO> binds = this.listJzJbxxDeviceBind(params);
        for (JzJbxxDeviceBindQueryResultDTO bind : binds) {
            LocateTimesDTO locateTimes = bind.getLocateTimes();
            List<LocateTimesDTO.Item> newLocateTimes = locateTimesByJzId.get(bind.getJzId());
            //之前没有设置或者是批量替换模式
            if (ObjectUtil.isEmpty(locateTimes) || Boolean.TRUE.equals(dto.getReplace())) {
                bind.setLocateTimes(new LocateTimesDTO(newLocateTimes));
                continue;
            }
            //增加
            locateTimes.addAll(newLocateTimes);
            bind.setLocateTimes(new LocateTimesDTO(locateTimes));
        }
        //批量更新
        this.jzJbxxDeviceBindDao.updateBatchById(binds.stream().map(e -> {
                    JzJbxxDeviceBind update = new JzJbxxDeviceBind();
                    update.setId(e.getId());
                    update.setLocateTimes(e.getLocateTimes());
                    update.setUpdateTime(new Date());
                    update.setUpdaterName(dto.getUpdaterName());
                    update.setUpdaterUid(dto.getUpdaterUid());
                    return update;
                }).collect(toList())
        );
        return binds;
    }

    @Override
    public JzJbxxDeviceBindQueryResultDTO deleteLocateTimes(LocateTimesDeleteDTO dto) {
        //查询绑定信息
        JzJbxxDeviceBindSimpleQueryParamDTO params = this.getQueryParam4App();
        params.setJzId(dto.getJzId());
        List<JzJbxxDeviceBindQueryResultDTO> binds = this.listJzJbxxDeviceBind(params);
        if (ObjectUtil.isEmpty(binds) || ObjectUtil.isEmpty(binds.get(0).getLocateTimes())) {
            throw new PubException("没有匹配的绑定记录,请确认参数是否正确!");
        }
        JzJbxxDeviceBindQueryResultDTO bind = binds.get(0);
        //更新信息
        JzJbxxDeviceBind update = new JzJbxxDeviceBind();
        update.setId(bind.getId());
        update.setLocateTimes(new LocateTimesDTO(
                bind.getLocateTimes().stream().filter(e -> !dto.getLocateTimeIds().contains(e.getId())).collect(toList())
        ));
        update.setUpdateTime(new Date());
        update.setUpdaterName(dto.getUpdaterName());
        update.setUpdaterUid(dto.getUpdaterUid());
        this.jzJbxxDeviceBindDao.updateJzJbxxDeviceBind(update);

        return JzJbxxDeviceBindConverter.from(update);
    }

    @Override
    public void updateFirstLocateTime(List<JzJbxxDeviceBindUpdateDTO> dtos) {
        if (ObjectUtil.isEmpty(dtos)) {
            return;
        }
        this.jzJbxxDeviceBindDao.updateBatchById(dtos.stream().map(JzJbxxDeviceBindConverter::from).collect(toList()));
    }

    @Override
    public List<String> listJzIds(JzJbxxDeviceBindSimpleQueryParamDTO dto) {
        if (dto == null) {
            dto = new JzJbxxDeviceBindSimpleQueryParamDTO();
        }
        if(ObjectUtil.isEmpty(dto.getMdy())){
            dto.setMdy(false);
        }

        return jzJbxxDeviceBindDao.listJzIds(dto);
    }


    @Override
    public List<JzJbxxDeviceBindQueryResultDTO> listJzJbxxDeviceBind(JzJbxxDeviceBindSimpleQueryParamDTO dto) {
        return this.jzJbxxDeviceBindDao.listJzJbxxDeviceBind(dto);
    }


    @Override
    @DictConvert
    public QueryPageResultDTO<JzJbxxDeviceBindQueryResultDTO> pageJzJbxxDeviceBind(JzJbxxDeviceBindSimpleQueryParamDTO dto, PageParamDTO page) {
        return jzJbxxDeviceBindDao.pageJzJbxxDeviceBind(dto, page);
    }

    @Override
    @DictConvert
    public List<JzJbxxDeviceBindWithJzJbxxDTO> listJzJbxxDeviceBindGroup(JzJbxxDeviceBindQueryParamDTO dto) {
        this.fillParam(dto);
        List<JzJbxxDeviceBindWithJzJbxxDTO> list = this.jzJbxxDeviceBindMapper.list(dto);
        this.fillResult(list, dto);
        return list;
    }


    @Override
    @DictConvert
    public QueryPageResultDTO<JzJbxxDeviceBindWithJzJbxxDTO> pageJzJbxxDeviceBindGroup(JzJbxxDeviceBindQueryParamDTO dto, PageParamDTO page) {
        this.fillParam(dto);
        QueryPageResultDTO<JzJbxxDeviceBindWithJzJbxxDTO> result = QueryWrapperConverter.fromPage(this.jzJbxxDeviceBindMapper.page(
                        new Page<>(page.getPage(), page.getSize()), dto
                )
        );
        this.fillResult(result.getList(), dto);
        return result;
    }


    private void fillParam(JzJbxxDeviceBindQueryParamDTO dto) {
        dto.setGroupConcatDeviceCode(GroupConcatUtil.format("b.device_code", "b.channel", "/"));
        dto.setGroupConcatChannel(GroupConcatUtil.format("b.channel", "b.channel", "/"));
    }

    private void fillResult(List<JzJbxxDeviceBindWithJzJbxxDTO> list, JzJbxxDeviceBindQueryParamDTO dto) {
        //未开通
        if (ObjectUtil.isNotEmpty(dto.getChannel())
                && Boolean.FALSE.equals(dto.getTurnOn())
                && ObjectUtil.isNotEmpty(list)) {
            for (JzJbxxDeviceBindWithJzJbxxDTO bind : list) {
                bind.setChannels(dto.getChannel());
            }
        }
        //越界距离
        for (JzJbxxDeviceBindWithJzJbxxDTO bind : list) {
            if (bind.getCrossDistance() == null) {
                bind.setCrossDistance(positionConfig.getCrossDistance());
            }
        }
    }


    public JzJbxxDeviceBindSimpleQueryParamDTO getQueryParam4App() {
        return new JzJbxxDeviceBindSimpleQueryParamDTO()
                .setChannelType(ChannelType.APP.getCode());
    }

    @Override
    public boolean existsJzJbxxDeviceBind4App(String jzId) {
        return jzJbxxDeviceBindDao.exists(jzId, ChannelType.APP.getCode());
    }

    @Override
    public JzJbxxDeviceBindWithLocateTimesDTO getJzJbxxDeviceBind4LocateTimes(String jzId) {
        JzJbxxDeviceBindSimpleQueryParamDTO params = new JzJbxxDeviceBindSimpleQueryParamDTO();
        params.setJzId(jzId);
        List<JzJbxxDeviceBindWithLocateTimesDTO> binds = this.listJzJbxxDeviceBind4LocateTimes(params);
        if (ObjectUtil.isEmpty(binds)) {
            throw new PubException("请联系工作人员在平台开通APP定位");
        }
        return binds.get(0);
    }


    @Override
    public List<JzJbxxDeviceBindWithLocateTimesDTO> listJzJbxxDeviceBind4LocateTimes(JzJbxxDeviceBindSimpleQueryParamDTO dto) {
        BeanUtil.copyProperties(getQueryParam4App(), dto, CopyOptions.create().ignoreNullValue());
        return this.jzJbxxDeviceBindDao.listJzJbxxDeviceBind4LocateTimes(dto);
    }

    /**
     * 保存历史记录+发送消息
     *
     * @param bind      最新的绑定信息
     * @param isDelPure 纯粹删除，则需要删除locateTimes配置
     */
    private void syncDeviceBind(CRUDEnum crud,
                                JzJbxxDeviceBindQueryResultDTO bind,
                                String updaterUid,
                                String updaterName,
                                boolean isDelPure) {
        JzJbxxQueryResultDTO jzJbxx = jzJbxxService.getJzJbxxById(bind.getJzId());
        bind.setJgId(jzJbxx.getJgId());
        bind.setJgCode(jzJbxx.getJgCode());
        //保存历史记录
        JzJbxxDeviceBindHistoryAddDTO history = BeanUtil.copyProperties(bind, JzJbxxDeviceBindHistoryAddDTO.class);
        history.setCrud(crud.getCode());
        history.setDeviceBindId(bind.getId());
        history.setCreatorUid(updaterUid);
        history.setCreatorName(updaterName);
        this.jzJbxxDeviceBindHistoryService.saveJzJbxxDeviceBindHistory(history);
        //发送消息
        isDelPure = isDelPure && ChannelType.APP.getCode().equals(bind.getChannelType());
        kafkaTemplate.send(DEVICE_BIND, new JzJbxxDeviceBindMsgDTO(crud.getCode(), isDelPure, bind));
    }


    private void checkRepeat(CRUDEnum crud,
                             String jzId,
                             String deviceCode,
                             String channel) {
        JzJbxxDeviceBind exists = this.jzJbxxDeviceBindDao.getJzJbxxDeviceBindByDeviceCode(deviceCode, channel);
        if (exists == null) {
            return;
        }
        switch (crud) {
            case ADD:
                throw new PubException(String.format("已经存在绑定信息[%s]", deviceCode));
            case UPDATE_DEVICE_CODE:
                if (!exists.getJzId().equals(jzId)) {
                    throw new PubException(String.format("已经绑定至其他矫正对象[%s]", deviceCode));
                }
                break;
        }
    }

}
